# -*- coding: utf-8 -*-
# @Time    : 2016/9/13
# @Author  : xiaofei.luo

from sympy import expand, Symbol, Eq, solveset, sympify, S, FiniteSet, EmptySet, Function, Derivative, diff, solve
from mathsolver.functions.daoshu.daoshuyunsuan import HanShuDaoShuYunSuan
from mathsolver.functions.hanshu.helper import check_func
from mathsolver.functions.base.base import BaseFunction, new_latex
from mathsolver.functions.base.objects import BaseSinFunc, BaseValue, BasePoint, BaseNumber, BaseFunc, BaseEq
from mathsolver.functions.hanshu.dingyiyu import QiuDingYiYu
from mathsolver.functions.root.jiefangchen import JieFangChen


class DaoShuQieXianXieLv001(BaseFunction):
    """求在一点的切线斜率"""

    @staticmethod
    def is_zhixian(baseinput):
        """判断输入是否是直线 or func (曲线 BaseEq(['*', '*']))"""
        return isinstance(baseinput, (BaseFunc, BaseSinFunc)) or sympify(baseinput.value[0]).is_Atom

    def solver(self, *args):
        func_og = args[0]

        # 切点可能是 x=a 处或点 (a,b)
        if isinstance(args[1], BaseEq):
            x1 = args[1].sympify()[1]
        elif isinstance(args[1], BasePoint):
            x1, x2 = args[1].sympify()
        else:
            raise ValueError("unknown type")

        # 如果曲线是直线
        if self.is_zhixian(func_og):
            func = check_func(func_og)
            deriv = HanShuDaoShuYunSuan().solver(func).output[0]
            self.steps.append(["因为 {}".format(func.printing()),
                               "求导得 {}".format(deriv.printing())])

            xielu_final = expand(deriv.expression.subs(deriv.var, x1))
            self.steps.append(["把 {0}={1} 代入导函数得：".format(deriv.var, new_latex(x1)),
                               "{0}({1}) = {2}".format(deriv.name, new_latex(x1), new_latex(xielu_final))])

            self.steps.append(["所以切线斜率为 {}".format(new_latex(xielu_final)), ""])
        # 如果曲线是圆锥
        else:
            from sympy.abc import x, y
            f = Function('f')(x)
            yz_left_og, yz_right_og = sympify(func_og.value)
            yz_left, yz_right = yz_left_og.subs({y: f}), yz_right_og.subs({y: f})

            deriv_left, deriv_right = diff(yz_left, x), diff(yz_right, x)
            xielu = solve(deriv_left - deriv_right, Derivative(f, x))[0].subs({f: y})
            xielu_final = xielu.subs({x: x1, y: x2})
            self.steps.append(["", "对方程两边求导得 y' = %s，所以切线斜率为 %s " % (new_latex(xielu), new_latex(xielu_final))])

        self.output.append(BaseNumber(xielu_final))
        self.label.add("利用导数解决切线斜率问题")
        return self


class DaoShuQieXianXieLv002(BaseFunction):
    """已知切线斜率求切点坐标"""

    @staticmethod
    def filter_fangchenjie(jieji, dingyiyu):
        jie = jieji.output[0].value2pairs()  # [{x_0: -2}, {x_0: 3}] 的形式
        filtered = False

        def in_interval(value, qujian):
            return not isinstance(qujian.intersection(FiniteSet(value)), EmptySet)

        result = []
        for m in jie:
            if in_interval(list(m.values())[0], dingyiyu):
                result.append(m)
            else:
                filtered = True

        return result, filtered

    def solver(self, *args):
        func, xielu = check_func(args[0]), args[1]

        deriv = HanShuDaoShuYunSuan().solver(func).output[0]
        self.steps.append(["因为 {}".format(func.printing()),
                           "求导得 {}".format(deriv.printing())])

        # 设出切点坐标
        qiedian_x, qiedian_y = Symbol(str(deriv.var) + "_0"), Symbol(str(func.name) + "_0")
        output_left, output_right = deriv.expression.subs(deriv.var, qiedian_x), xielu.sympify()
        self.steps.append(["设切点为 ({0}, {1})".format(new_latex(qiedian_x), new_latex(qiedian_y)),
                           "则 {0} = {1}".format(new_latex(output_left), new_latex(output_right))])

        # 解方程
        fangchen = BaseEq([output_left, output_right])
        jie = JieFangChen().solver(fangchen)
        self.steps.extend(jie.steps)

        # 求函数定义域，求方程的解和定义域得交集
        dingyiyu = QiuDingYiYu().solver(func)

        # [{x_0: -2}, {x_0: 3}] 交集 <class 'sympy.sets.sets.Interval'>
        # print '*****', jie.output[0].toPairs(), type(dingyiyu.interval)
        jie_filtered, filtered = self.filter_fangchenjie(jie, dingyiyu)
        if filtered:
            p = " 或 ".join(["{} = {}".format(new_latex(list(m.keys())[0]), new_latex(list(m.values())[0])) for m in jie_filtered])
            self.steps.append(["函数的定义域为 {}".format(new_latex(dingyiyu)), "所以 " + p])
        # 输出
        out = []
        for m in jie_filtered:
            hengzuobiao = list(m.values())[0]
            zongzuobiao = func.expression.subs(func.var, hengzuobiao)
            point = BasePoint({"name": "", "value": [hengzuobiao, zongzuobiao]})
            out.append(point)

        self.steps.append(["切点的坐标为 " + " 或 ".join([point.printing() for point in out]), ""])
        if len(out) == 1:
            self.output.append(out[0])
        else:
            self.output.append(out)
        self.label.add("已知切线斜率求切点坐标")
        return self


class DaoShuQieXianXieLv0021(BaseFunction):
    """
    已知切线斜率求切点横坐标
    """
    def solver(self, *args):
        jie = DaoShuQieXianXieLv002().solver(*args)
        self.steps = jie.steps[:-1]
        jie_output = jie.output[0]
        if not isinstance(jie_output, list):
            jie_output = [jie_output]
        out = [str(point.sympify()[0]) for point in jie_output]
        for o in out:
            self.output.append(BaseNumber(o))
        self.label.add("已知切线斜率求切点横坐标")
        return self


class DaoShuQieXianXieLv003(BaseFunction):
    """求过一点的切线斜率"""

    def solver(self, *args):
        func, qiedian = args[0].to_func(), args[1]
        x1, x2 = qiedian.sympify()

        # 设出切点坐标
        x0 = str(func.var) + "_0"
        f_x0 = func.expression.subs(func.var, x0)
        self.steps.append(["设切点为 ({}, {})".format(x0, new_latex(f_x0)), ""])

        # 求出曲线导函数在某点处的导数值，即为切线的斜率
        deriv = HanShuDaoShuYunSuan().solver(func).output[0]
        f_d_x0 = deriv.expression.subs(deriv.var, x0)
        self.steps.append(["求导得 {}' = {}".format(func.name, new_latex(deriv.expression)),
                           "所以 {}' \\left. \\right|_{{{}={}}} = {}".format(func.name, func.var, new_latex(x0),
                                                                           new_latex(f_d_x0))])

        # 两点式表示斜率
        self.steps.append(
            ["则切线方程为 {} - {} = {}({} - {})".format(func.name, new_latex(f_x0), new_latex(f_d_x0), func.var, new_latex(x0)), ""])

        # 两种表示斜率方式相等，求出未知数（此为另一个元函数：解方程（组））；
        self.steps.append(["因为点 ({}, {}) 在切线上".format(x1, x2),
                           "所以 {} - {} = {}({} - {})".format(x2, new_latex(f_x0), new_latex(f_d_x0), x1, new_latex(x0))])
        x0_ans = solveset(Eq(x2 - f_x0, f_d_x0 * (x1 - sympify(x0))), domain=S.Reals)

        # 代入求出直线的斜率
        result = [f_d_x0.subs(sympify(x0), x) for x in x0_ans]
        self.steps.append(["解得 {} = {}".format(new_latex(x0), " 或 ".join(new_latex(x) for x in x0_ans)),
                           "所以切线斜率为 {}".format(" 或 ".join(new_latex(x) for x in result))])

        self.output.append(BaseValue(result))
        self.label.add("利用导数解决切线斜率问题")

        return self


if __name__ == '__main__':
    pass
